﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Slam.Configuration;
using Slam.Data;
using Slam.Exceptions;
using Slam.Logging;
using Microsoft.SharePoint;
using System.Text.RegularExpressions;

namespace Slam.SharePoint
{
	/// <summary>
	/// Defines SLAM-specific extension methods for SPListItem.  SLAM's target data source is a SQL Server database in the default implementations.
	/// </summary>
	public class DefaultListItemExtensions : IListItemExtensions
	{
		#region IListItemExtensions Members

		/// <summary>
		/// Verifies that SPListItems associated with the given SPListItem as contained in the given
		/// list of association fields have values in SLAM's target data source.
		/// 
		/// Due to database constraints, associated items must be in SLAM's target data source before the given
		/// listItem can be processed properly.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="associationFields">Associations to be verified in target data source</param>
		/// <param name="force">Process related ListItems whether or not they are represented in SLAM's target data source</param>
		public void EnsureSlamAssociations(SPListItem listItem, List<Field> associationFields, bool force)
		{
			List<AssociationField> associationFieldList = associationFields.Where(f => f is AssociationField).Cast<AssociationField>().ToList();
			foreach (AssociationField associationField in associationFieldList)
			{
				if (associationField is SqlMappedAssociationField)
				{
					foreach (SPListItem associatedListItem in listItem.GetLookupValueListItems(associationField.Name))
					{
						if (force || associatedListItem.VerifyItemRequiresSlamUpdate())
						{
							associatedListItem.SlamUpdate();
						}
						break;
					}
				}
			}
		}

		/// <summary>
		/// Gets a set of field values for given list of Fields from the given SPListItem.
		/// 
		/// The result includes SPListItem property values, Field values, and "inline" Association Field values if AssociationField items 
		/// are included in outputFields.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="outputFields">List of Fields for which to get values</param>
		/// <returns>Hashtable of field names and values</returns>
		public Hashtable GetFieldValues(SPListItem listItem, List<Field> outputFields)
		{
			Hashtable values = new Hashtable();
			List<Field> mappedFields = outputFields.Where(f => !(f is AssociationField)).ToList();
			List<AssociationField> associationFields = outputFields.Where(f => f is AssociationField).Cast<AssociationField>().ToList();

			foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(listItem))
			{
				Field currentField = mappedFields.Find(delegate(Field fieldToMatch) { return fieldToMatch.Name.Equals(property.Name, StringComparison.CurrentCultureIgnoreCase); });

				if (currentField != null && !String.IsNullOrEmpty(currentField.Name))
				{
					if (!values.ContainsKey(XmlSqlDataMapping.GetColumnName(currentField)))
					{
						values.Add(XmlSqlDataMapping.GetColumnName(currentField), property.GetValue(listItem) == null ? "" : property.GetValue(listItem).ToString());
					}
				}
			}

			foreach (SPField field in listItem.Fields)
			{
				Field currentField = mappedFields.Find(delegate(Field fieldToMatch) { return fieldToMatch.Name.Equals(field.InternalName, StringComparison.CurrentCultureIgnoreCase) || fieldToMatch.Name.Equals(field.Title, StringComparison.CurrentCultureIgnoreCase); });

				if (currentField != null && !String.IsNullOrEmpty(currentField.Name))
				{
					try
					{
						if (!values.ContainsKey(XmlSqlDataMapping.GetColumnName(currentField)))
						{
							values.Add(XmlSqlDataMapping.GetColumnName(currentField), listItem[field.Id] == null ? "" : listItem[field.Id].ToString());
						}
					}
					catch (Exception ex)
					{
						ILogger logger = ComponentService.GetInstance<ILogger>();
						logger.LogMessage("Exception", String.Format("The given ListItem is reporting to have the field: {0}, but its value is inaccessible.{1}Matched Display Name: {2}{1}Matched Internal Name: {3}{1}Matched Field ID: {4}{1}Exception:{5}", currentField.Name, Environment.NewLine, field.Title, field.InternalName, field.Id.ToString(), ex.ToString()));
					}
				}
			}

			if (mappedFields.Exists(f => f.Name == "ListID"))
			{
				SPWeb site = listItem.Web;
				SPSite siteCollection = site.Site;
				SPList list = listItem.ParentList;

				IConfigurationManager slamConfiguration = ComponentService.GetInstance<IConfigurationManager>();
				DataMapping currentDataMapping = slamConfiguration.DataMapping;
				string siteCollectionUrl = siteCollection.ServerRelativeUrl.TrimStart('/');
				if (!String.IsNullOrEmpty(siteCollectionUrl) && slamConfiguration.DataMappings.ContainsKey(siteCollectionUrl))
				{
					currentDataMapping = slamConfiguration.DataMappings[siteCollectionUrl];
				}

				string connectionString = "";
				if (currentDataMapping is XmlSqlDataMapping)
				{
					connectionString = ((XmlSqlDataMapping)currentDataMapping).GetConnectionString();
				}

				if (String.IsNullOrEmpty(connectionString))
				{
					connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").Default;
				}
				else
				{
					connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").GetConnectionString(connectionString);
				}

				SqlDataRepository database = new SqlDataRepository(connectionString);
				database.UpdateLocation(siteCollection.ID.ToString(), siteCollection.ServerRelativeUrl,
										site.ID.ToString(), site.Name, site.Title, site.ServerRelativeUrl.Substring(siteCollection.ServerRelativeUrl.Length),
										list.ID.ToString(), "/" + list.DefaultView.Url, "/" + list.Forms[PAGETYPE.PAGE_NEWFORM].Url, 
										"/" + list.Forms[PAGETYPE.PAGE_EDITFORM].Url, "/" + list.Forms[PAGETYPE.PAGE_DISPLAYFORM].Url);

				values.Add("ListID", list.ID.ToString());
			}

			EnsureSlamAssociations(listItem, outputFields, false);

			foreach (AssociationField associationField in associationFields)
			{
				if (associationField is SqlMappedAssociationField)
				{
					foreach (SPListItem associatedListItem in listItem.GetLookupValueListItems(associationField.Name))
					{	
						values.Add(((SqlMappedAssociationField)associationField).SecondAssociatedTypeIdColumn, associatedListItem.GetUniqueId());
						break;
					}
				}
			}

			return values;
		}

		private static object _generateIdLock = new object();

		/// <summary>
		/// Gets the unique identifier of the given SPListItem.  The ListItem can be a new list item that has not yet been assigned an ID.
		/// 
		/// The unique identifier of an SPListItem is either a SLAM ID, if the configured type for the given listItem has a TypeIdDefinition
		/// specifying the use of a SLAM ID, or a the UniqueId.
		/// </summary>
		/// <param name="listItem">Source ListItem</param>
		/// <param name="generateNewIdIfNull">Indicates whether a new ID should be generated if the given ListItem does not have a relevant ID</param>
		/// <param name="throwExceptionIfNull">Indicates whether exceptions should be thrown on error</param>
		/// <returns>Unique List Item identifier</returns>
		public object GetUniqueId(SPListItem listItem, bool generateNewIdIfNull, bool throwExceptionIfNull)
		{
			object uniqueId = null;
			bool defaultIsSlamId = false;
			try
			{
				IConfigurationManager slamConfiguration = ComponentService.GetInstance<IConfigurationManager>();
				DataMapping currentDataMapping = slamConfiguration.DataMapping;
				string siteCollection = listItem.Web.Site.ServerRelativeUrl.TrimStart('/');
				if (!String.IsNullOrEmpty(siteCollection) && slamConfiguration.DataMappings.ContainsKey(siteCollection))
				{
					currentDataMapping = slamConfiguration.DataMappings[siteCollection];
				}
				else if (String.IsNullOrEmpty(siteCollection) && currentDataMapping != slamConfiguration.DataMappings["DEFAULTMAPPING"])
				{
					currentDataMapping = slamConfiguration.DataMappings["DEFAULTMAPPING"];
				}
				
				string typeIdDefinitionName = "";
				TypeIdDefinition typeIdDefinition = new TypeIdDefinition();
				if (currentDataMapping is XmlSqlDataMapping)
				{
					typeIdDefinitionName = listItem.GetSlamConfiguredType().TypeIdDefinition;
					if (String.IsNullOrEmpty(typeIdDefinitionName))
					{
						typeIdDefinitionName = ((XmlSqlDataMapping)currentDataMapping).GetTypeIdDefinitionName();
					}
					typeIdDefinitionName = ((XmlSqlDataMapping)currentDataMapping).GetTypeIdDefinitionName();
				}

				if (String.IsNullOrEmpty(typeIdDefinitionName))
				{
					typeIdDefinition = slamConfiguration.GetSection<TypeIdDefinitionSection>("TypeIdDefinitions").Default;
				}
				else
				{
					typeIdDefinition = slamConfiguration.GetSection<TypeIdDefinitionSection>("TypeIdDefinitions").GetTypeIdDefinition(typeIdDefinitionName);
				}

				defaultIsSlamId = typeIdDefinition.IsPrimaryKey;

				//If there is no TypeIdDefinition and UniqueId is present, or if there is a TypeIdDefinition but
				//the defined ID is neither a Primary Key nor replacing SharePoint IDs, then use listItem.UniqueId
				if (!generateNewIdIfNull && 
					((String.IsNullOrEmpty(typeIdDefinition.Name) && listItem["UniqueId"] != null) ||
					(!String.IsNullOrEmpty(typeIdDefinition.Name) && listItem["UniqueId"] != null &&
					 !typeIdDefinition.IsPrimaryKey && !typeIdDefinition.ReplaceSharePointIds)))
				{
					uniqueId = listItem.UniqueId;
				}
				else if (listItem.Fields.ContainsField("SLAM ID"))
				{
					if (listItem["SLAM ID"] != null)
					{
						uniqueId = listItem["SLAM ID"];
					}
					else if (generateNewIdIfNull && !String.IsNullOrEmpty(typeIdDefinition.Name))
					{
						switch (typeIdDefinition.ValueFormat)
						{
							case TypeIdDefinitionValueFormat.Guid:
								uniqueId = Guid.NewGuid();
								break;
							case TypeIdDefinitionValueFormat.Int:
							case TypeIdDefinitionValueFormat.Hex:
								int slamIdSeed = 0;
								SPSite storeSiteCollection = null;
								SPWeb storeSite = null;
								Uri currentItemWebUri = new Uri(listItem.Web.Url);
								try
								{
									SPSecurity.RunWithElevatedPrivileges(delegate()
									{
										string defaultSiteCollection = ((XmlSqlDataMapping)slamConfiguration.DataMappings["DEFAULTMAPPING"]).SiteCollection;
										if (String.IsNullOrEmpty(defaultSiteCollection))
										{
											storeSiteCollection = new SPSite(String.Format("{0}://{1}/", currentItemWebUri.Scheme, currentItemWebUri.Authority));
										}
										else
										{
											storeSiteCollection = new SPSite(String.Format("{0}://{1}/{2}/", currentItemWebUri.Scheme, currentItemWebUri.Authority, defaultSiteCollection));
										}
										storeSite = storeSiteCollection.RootWeb;
									});
								}
								catch {} //if an exception is thrown, it is likely a permission issue

								string connectionString = "";
								if (currentDataMapping is XmlSqlDataMapping)
								{
									connectionString = ((XmlSqlDataMapping)currentDataMapping).GetConnectionString();
								}

								if (String.IsNullOrEmpty(connectionString))
								{
									connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").Default;
								}
								else
								{
									connectionString = slamConfiguration.GetSection<ConnectionStringSection>("ConnectionStrings").GetConnectionString(connectionString);
								}

								if (storeSiteCollection != null && storeSite.Exists)
								{
									SqlDataRepository database = new SqlDataRepository(connectionString);
									lock (_generateIdLock)
									{
										if (storeSite.Properties["SlamIdNumber"] != null)
										{
											slamIdSeed = Int32.Parse(storeSite.Properties["SlamIdNumber"]);
											slamIdSeed++;
											storeSite.Properties["SlamIdNumber"] = slamIdSeed.ToString();
										}
										else
										{
											slamIdSeed = 1;
											storeSite.Properties.Add("SlamIdNumber", slamIdSeed.ToString());
										}
										storeSite.AllowUnsafeUpdates = true;
										storeSite.Properties.Update();
									}
								}
								else
								{
									slamIdSeed = 1;
								}
								if (typeIdDefinition.ValueFormat == TypeIdDefinitionValueFormat.Int)
								{
									uniqueId = slamIdSeed;
								}
								else
								{
									uniqueId = slamIdSeed.ToString("X").ToLower();
								}

								if (storeSite.Exists)
								{
									storeSite.Dispose();
								}

								if (storeSiteCollection != null)
								{
									storeSiteCollection.Dispose();
								}
								break;
							default:
								break;
						}

						if (!String.IsNullOrEmpty(typeIdDefinition.ValuePrefix))
						{
							uniqueId = typeIdDefinition.ValuePrefix + uniqueId.ToString();
						}
						if (!String.IsNullOrEmpty(typeIdDefinition.ValueSuffix))
						{
							uniqueId = uniqueId.ToString() + typeIdDefinition.ValueSuffix;
						}
					}
				}
			}
			catch { }
			finally
			{
				if (uniqueId == null && listItem["UniqueId"] != null && !defaultIsSlamId)
				{
					uniqueId = listItem.UniqueId;
				}
				else if(uniqueId == null && throwExceptionIfNull)
				{
					throw new SlamException("ID for item, " + listItem.GetTitle() + ", not found.");
				}
			}

			return uniqueId;
		}

		private Regex slamPropertyFormat = new Regex("(?<=##)(.*?)(?=##)", RegexOptions.Compiled);

		public string GetSlamPropertyValue(SPListItem listItem, string name)
		{
			string value = "";
			if (listItem.Fields.ContainsField("SLAM Properties"))
			{
				foreach (Match valueMatch in slamPropertyFormat.Matches(Convert.ToString(listItem["SLAM Properties"])))
				{
					string[] parts = valueMatch.Value.Split(';');
					if (parts[0].Equals(name, StringComparison.OrdinalIgnoreCase))
					{
						value = parts[1];
						break;
					}
				}
			}
			return value;
		}

		public void SetSlamProperty(SPListItem listItem, string name, object value)
		{
			bool propertyUpdated = false;
			string fieldValue = "##";
			foreach (Match valueMatch in slamPropertyFormat.Matches(Convert.ToString(listItem["SLAM Properties"])))
			{
				string[] parts = valueMatch.Value.Split(';');
				if (parts[0].Equals(name, StringComparison.OrdinalIgnoreCase))
				{
					fieldValue += String.Format("{0};{1}##", parts[0], value);
					propertyUpdated = true;
				}
				else
				{
					fieldValue += String.Format("{0}##", valueMatch.Value);
				}
			}

			if (!propertyUpdated)
			{
				fieldValue += String.Format("{0};{1}##", name, value);
			}

			listItem["SLAM Properties"] = fieldValue;
		}

		#endregion
	}
}
